#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QList>
#include <QRegExp>
#include <QStack>
#include <QTextStream>

#include "filecopier.h"
#include "common/fstraverseiterator.h"

// THIS IS NOT THE PLACE FOR THIS DEFINE!
#define TXTSUBST_KWORDREGEXP       "@[a-zA-Z]+@"


FileCopier::FileCopier(const QString                & source,
                       const QString                & destination,
                       const QMap<QString, QString> * filenameSubstitutions,
                       const QSet<QString>          * ignorePatterns,
                       const QMap<QString, QString> * keywordSubstitutions,
                       GeneratorOutputView          & outputView)
    : m_source(source)
    , m_destination(destination)
    , m_filenameSubstitutions(filenameSubstitutions)
    , m_keywordSubstitutions(keywordSubstitutions)
    , m_outputView(outputView)
    , m_keywordRegExp(TXTSUBST_KWORDREGEXP)
{
    if (ignorePatterns != NULL)
    {
        foreach (QString ignorePattern, *ignorePatterns)
        {
            // TODO should the case sensitivity and wildcard / wildcardunix
            //      be made platform dependent too?
            QRegExp
                ignoreRegExp(ignorePattern,
                             Qt::CaseSensitive,
                             QRegExp::Wildcard);
            m_ignoreRegExps.push_back(ignoreRegExp);
        }
    }
}


bool FileCopier::LooksLikeDir(const QFileInfo & file)
{
    bool
        rv = true;

    if (file.exists())
    {
        rv = file.isDir();
    }
    else
    {
        // path does not exist, so we have to guess
        // based on its look whether it can be thought
        // as a directory

        QString
            path = file.absoluteFilePath();

        bool
            suffixLess = file.suffix().length() == 0,
            hasTrailingDirSeparator = path.at(path.length() - 1) == QDir::separator();

        rv = suffixLess || hasTrailingDirSeparator;
    }

    return rv;
}


bool FileCopier::copyFileVerbatim(const QFileInfo & srcFileInfo,
                                  const QFileInfo & dstFileInfo)
{
    bool
        rv = true;

    if (dstFileInfo.exists())
    {
        rv = QFile(dstFileInfo.absoluteFilePath()).remove();
    }

    rv = rv && QFile::copy(srcFileInfo.absoluteFilePath(),
                           dstFileInfo.absoluteFilePath());

    if (!rv)
    {
        QString
            msg("Error: Could not copy %1 to %2 verbatim.");
        m_outputView.printOutput(msg.arg(srcFileInfo.absoluteFilePath(),
                                         dstFileInfo.absoluteFilePath()));
    }

    return rv;
}


QString FileCopier::substitute(const QString & line)
{
    QString
        rv = line;

    int
        pos;
    while ((pos = m_keywordRegExp.indexIn(rv)) != -1)
    {
        QString
            key = m_keywordRegExp.cap(0);

        if (m_keywordSubstitutions->contains(key))
        {
            QString
                value = (*m_keywordSubstitutions)[key];
            rv.replace(pos,
                       key.length(),
                       value);
        }
        else
        {
            QString
                msg("Warning: No substitution defined for keyword '%1'.");
            m_outputView.printOutput(msg.arg(key));
            break;
        }
    }

    return rv;
}


bool FileCopier::copyFileSubstituting(const QFileInfo & srcFileInfo,
                                      const QFileInfo & dstFileInfo)
{
    bool
        rv = false;

    QFile
        srcFile(srcFileInfo.absoluteFilePath());
    rv = srcFile.open(QIODevice::ReadOnly | QIODevice::Text);

    if (rv)
    {
        QFile
            dstFile(dstFileInfo.absoluteFilePath());
        rv = dstFile.open(QIODevice::WriteOnly | QIODevice::Text);


        if (rv)
        {
            QTextStream
                srcStream(&srcFile);
            QTextStream
                dstStream(&dstFile);

            while (rv && !srcStream.atEnd())
            {
                QString
                    line = srcStream.readLine();

                rv = srcStream.status() == QTextStream::Ok;
                if (rv)
                {
                    line = substitute(line);
                    dstStream << line << endl;
                    rv = dstStream.status() == QTextStream::Ok;

                    if (!rv)
                    {
                        QString
                            msg("Error: Could not write to %1.");
                        m_outputView.printOutput(msg.arg(dstFileInfo.absoluteFilePath()));
                    }
                }
                else
                {
                    QString
                        msg("Error: Could not read from %1.");
                    m_outputView.printOutput(msg.arg(srcFileInfo.absoluteFilePath()));
                }
            }
        }
        else
        {
            QString
                msg("Error: Could not open destination %1 for writing.");
            m_outputView.printOutput(msg.arg(dstFileInfo.absoluteFilePath()));
        }
    }
    else
    {
        QString
            msg("Error: Could not open source %1 for reading.");
        m_outputView.printOutput(msg.arg(srcFileInfo.absoluteFilePath()));
    }

    return rv;
}


bool FileCopier::fileToBeIgnored(const QFileInfo & dstFileInfo)
{
    bool
        rv = false;

    QString
        fileName =  dstFileInfo.fileName();

    foreach (QRegExp ignoreRegExp, m_ignoreRegExps)
    {
        if ((rv = (ignoreRegExp.indexIn(fileName) != -1)))
        {
            break;
        }
    }

    return rv;
}


bool FileCopier::copyFile(const QFileInfo & srcFileInfo,
                          const QDir      & dstBaseDir)
{
    QString
        msg("Copying file %1 to directory %2");
    m_outputView.printOutput(msg.arg(srcFileInfo.absoluteFilePath(),
                                     dstBaseDir.absolutePath()));

    bool
        rv = true;

    // filename substitution, if enabled
    QString
        baseName(srcFileInfo.baseName()),
        suffix(srcFileInfo.suffix());

    if (m_filenameSubstitutions != NULL
        && m_filenameSubstitutions->contains(baseName))
    {
        baseName = (*m_filenameSubstitutions)[baseName];
    }

    QString
        dstFilePath = dstBaseDir.absolutePath();
    dstFilePath += QDir::separator();
    dstFilePath += baseName;
    if (suffix.length() > 0)
    {
        dstFilePath += '.';
        dstFilePath += suffix;
    }

    QFileInfo
        dstFileInfo(dstFilePath);

    if (m_keywordSubstitutions != NULL
        && !m_keywordSubstitutions->empty()
        && !fileToBeIgnored(dstFileInfo))
    {

        rv = copyFileSubstituting(srcFileInfo,
                                  dstFileInfo);
    }
    else
    {
        rv = copyFileVerbatim(srcFileInfo,
                              dstFileInfo);
    }

    return rv;
}


bool FileCopier::copyDir(const QDir & srcBaseDir,
                         const QDir & dstBaseDir)
{
    QString
        msg("Copying directory content of %1 to %2");
    m_outputView.printOutput(msg.arg(srcBaseDir.absolutePath(),
                                     dstBaseDir.absolutePath()));

    bool
        rv = true,
        first = true;
    QDir
        curSrcDir = srcBaseDir,
        curDstDir = dstBaseDir;

    // Instead of recursive call here, we use an iterator that traverses
    // recursively - and keep tracking the current src/dst directories.
    // This iterator (DirsBoth) will enumerate each directory twice: once
    // before its content and once after it. We'll use the first even
    // to create dst dir and descend both src/dst dirs, and use the second
    // to go back up.
    FSTraverseIterator
        fsIt(srcBaseDir.absolutePath(),
             FSTraverseIterator::DirsBoth | FSTraverseIterator::Files),
        fsEnd;

    for (; fsIt != fsEnd && rv; ++fsIt)
    {
        QFileInfo
            fi = *fsIt;
        if (fi.isFile())
        {
            rv = copyFile(fi,
                          curDstDir);
        }
        else // fi.isDir()
        {
            if (first) // the very beginning of iteration
            {
                first = false;
                if (!curDstDir.exists(curDstDir.absolutePath()))
                    rv = curDstDir.mkdir(curDstDir.absolutePath());
            }
            else if (curSrcDir.absolutePath() == fi.absoluteFilePath())
            {
                // done with all the kids of cur src/dst dirs: "cd .."
                curSrcDir.cdUp();
                curDstDir.cdUp();
            }
            else
            {
                // for src/dst dirst: "cd <child-dir-name>"
                QString
                    dirName = QDir(fi.absoluteFilePath()).dirName();
                rv = curSrcDir.cd(dirName);
                if (rv)
                {
                    if (!curDstDir.exists(dirName))
                        rv = curDstDir.mkdir(dirName);

                    if (rv)
                        rv = curDstDir.cd(dirName);
                }
            }
        }
    }

    return rv;
}


bool FileCopier::copy()
{
    bool
        rv = false;

    QFileInfo
        srcFileInfo(m_source),
        dstFileInfo(m_destination);

    if (srcFileInfo.exists() && srcFileInfo.isDir())
    {
        if (dstFileInfo.exists() && dstFileInfo.isFile())
        {
            QString
                msg("Error: Source %1 is dir, destination %1 is file.");
            m_outputView.printOutput(msg.arg(m_source, m_destination));
        }
        else
        {
            m_srcBaseDir = QDir(m_source);
            m_dstBaseDir = QDir(m_destination);

            rv = copyDir(m_srcBaseDir, m_dstBaseDir);
        }
    }
    else if(srcFileInfo.exists() && srcFileInfo.isFile())
    {
        m_srcBaseDir = QDir(srcFileInfo.absoluteDir());

        if (LooksLikeDir(dstFileInfo))
        {
            m_dstBaseDir = QDir(dstFileInfo.absoluteFilePath());

            rv = m_dstBaseDir.mkpath(m_dstBaseDir.absolutePath())
                 && copyFile(srcFileInfo,
                             m_dstBaseDir);
        }
        else
        {
            if (srcFileInfo.suffix() != dstFileInfo.suffix())
            {
                QString
                    msg("Error: Source file %1 suffix mismatches "
                        "destination %2 file suffix.");
                m_outputView.printOutput(msg.arg(m_source,
                                                 m_destination));
            }
            else
            {
                m_dstBaseDir = QDir(dstFileInfo.absoluteDir());

                rv = m_dstBaseDir.mkpath(m_dstBaseDir.absolutePath())
                     && copyFile(srcFileInfo,
                                 m_dstBaseDir);
            }
        }
    }
    else
    {
        QString
            msg("Error: Source %1 does not exist or cannot handle it");
        m_outputView.printOutput(msg.arg(m_source));
    }

    return rv;
}

